package org.misty.redis.client;

import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Misty on 2020-05-31
 */
@Slf4j
@ToString(onlyExplicitlyIncluded = true)
public class RedisTemplateAdapter implements RedisClient, IScriptLoader<RedisScript<?>> {
    private final static ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
    private final RedisTemplate<String, ?> redisTemplate;
    @ToString.Include
    private final String hash = Integer.toHexString(System.identityHashCode(this));
    @Getter
    @ToString.Include
    private String host;
    @Getter
    @ToString.Include
    private int port;

    public RedisTemplateAdapter(RedisTemplate<String, ?> redisTemplate) {
        this.redisTemplate = redisTemplate;
        try {
            RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
            if (factory instanceof LettuceConnectionFactory) {
                LettuceConnectionFactory fac = (LettuceConnectionFactory) factory;
                host = fac.getHostName();
                port = fac.getPort();
            } else if (factory instanceof JedisConnectionFactory) {
                JedisConnectionFactory fac = (JedisConnectionFactory) factory;
                host = fac.getHostName();
                port = fac.getPort();
            }
        } catch (Exception ignore) {
        }
    }

    @Override
    public RedisScript<?> scriptLoad(String script, Class<?> resultType) {
        Object rs = cache.get(script);
        if (!(rs instanceof RedisScript)) {
            while (true) {
                Object old = cache.putIfAbsent(script, Boolean.TRUE);
                if (old == Boolean.TRUE) { // 没抢到锁，概率最高
                    Thread.yield();
                } else if (old instanceof RedisScript) {
                    rs = old;
                    break;
                } else if (old == null) { // 抢到锁，概率最低
                    RedisScript<?> ns = RedisScript.of(script, resultType);
                    log.debug("load script: {}\n<<<===\n{}\n===>>>", ns.getSha1(), script);
                    cache.put(script, rs = ns);
                    break;
                } else {
                    throw new RuntimeException("不兼容的数据: " + old);
                }
            }
        }
        return (RedisScript<?>) rs;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T eval(String script, Class<T> resultType, List<String> keys, String... args) {
        RedisScript<T> rs = (RedisScript<T>) scriptLoad(script, resultType);
        return redisTemplate.execute(rs, keys, (Object[]) args);
    }

    @SuppressWarnings("unchecked")
    static RedisTemplateAdapter create(Object pool) {
        return new RedisTemplateAdapter((RedisTemplate<String, ?>) pool);
    }
}
